package dao

import (
	"context"
	"errors"
	"gitee.com/chicken-c/go-learn/webook/internal/domain"
	"gitee.com/chicken-c/go-learn/webook/pkg/logger"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

type ArticleDAO interface {
	Insert(ctx context.Context, art Article) (int64, error)
	Update(ctx context.Context, art Article) error
	Sync(ctx *gin.Context, art Article) (int64, error)
	SyncStatus(ctx *gin.Context, artId int64, authorID int64) error
	GetByAuthorId(ctx *gin.Context, offset int64, limit int64, id int64) ([]Article, error)
	GetById(ctx *gin.Context, artId int64) (Article, error)
	GetPubById(ctx *gin.Context, id int64) (PublishedArticle, error)
	ListPub(ctx context.Context, start time.Time, offset int64, limit int64) ([]PublishedArticle, error)
}

type GORMArticleDAO struct {
	db *gorm.DB
	l  logger.Logger
}

func (g *GORMArticleDAO) ListPub(ctx context.Context, start time.Time, offset int64, limit int64) ([]PublishedArticle, error) {
	const ArticleStatusPublished = 2
	var res []PublishedArticle
	err := g.db.WithContext(ctx).Model(&PublishedArticle{}).
		Where("utime < ? AND status = ?", start, ArticleStatusPublished).Offset(int(offset)).Limit(int(limit)).
		Find(&res).Error
	return res, err
}

func NewGORMArticleDAO(db *gorm.DB, l logger.Logger) ArticleDAO {
	return &GORMArticleDAO{
		db: db,
		l:  l,
	}
}

func (g *GORMArticleDAO) GetPubById(ctx *gin.Context, id int64) (PublishedArticle, error) {
	var art PublishedArticle
	err := g.db.WithContext(ctx).Model(&PublishedArticle{}).
		Where("id=?", id).Find(&art).Error
	if err != nil {
		return PublishedArticle{}, err
	}

	return art, nil
}

func (g *GORMArticleDAO) GetById(ctx *gin.Context, artId int64) (Article, error) {
	var art Article
	err := g.db.WithContext(ctx).Model(&Article{}).
		Where("id=?", artId).Find(&art).Error
	if err != nil {
		return Article{}, err
	}

	return art, nil
}

func (g *GORMArticleDAO) GetByAuthorId(ctx *gin.Context, offset int64, limit int64, id int64) ([]Article, error) {
	var arts []Article
	err := g.db.WithContext(ctx).Model(&Article{}).
		Offset(int(offset)).
		Limit(int(limit)).
		Where("author_id=?", id).
		Order("utime DESC").
		Find(&arts).Error
	if err != nil {
		return nil, err
	}

	return arts, nil
}

func (g *GORMArticleDAO) SyncStatus(ctx *gin.Context, artId int64, authorID int64) error {
	now := time.Now().UnixMilli()
	err := g.db.Transaction(func(tx *gorm.DB) error {
		//更新制作库
		res := tx.WithContext(ctx).Model(&Article{}).Where("id=? AND author_id=?", artId, authorID).
			Updates(map[string]any{
				"status": domain.ArticleStatusPrivate.Uint8(),
				"utime":  now,
			})
		if res.Error != nil {
			return res.Error
		}
		if res.RowsAffected == 0 {
			g.l.Warn("帖子ID和作者ID不一致")
			return errors.New("artId错误或者authorID错误")
		}

		//更新线上库
		err := tx.WithContext(ctx).Model(&PublishedArticle{}).Where("id=?", artId).
			Updates(map[string]any{
				"status": domain.ArticleStatusPrivate.Uint8(),
				"utime":  now,
			}).Error
		if err != nil {
			return err
		}

		return nil
	})

	return err
}

func (g *GORMArticleDAO) Sync(ctx *gin.Context, art Article) (int64, error) {
	var (
		id = art.Id
	)
	now := time.Now().UnixMilli()
	art.Utime = now

	//gorm的闭包，开启事务
	err := g.db.Transaction(func(tx *gorm.DB) error {
		//保存制作库
		var err error
		if id == 0 {
			id, err = g.Insert(ctx, art)
		} else {
			err = g.Update(ctx, art)
		}
		if err != nil {
			return err
		}

		art.Id = id
		publishArt := PublishedArticle{
			Article: art,
		}
		publishArt.Ctime = now
		publishArt.Utime = now
		//upsert操作，先插入，当发送冲突时，更新下面三个列
		err = g.db.Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"title":   art.Title,
				"content": art.Content,
				"status":  art.Status,
				"utime":   art.Utime,
			}),
		}).Create(&publishArt).Error

		return err
	})

	return id, err
}

func (g *GORMArticleDAO) Insert(ctx context.Context, art Article) (int64, error) {
	t := time.Now().UnixMilli()
	art.Ctime = t
	art.Utime = t
	err := g.db.WithContext(ctx).Create(&art).Error
	return art.Id, err
}

func (g *GORMArticleDAO) Update(ctx context.Context, art Article) error {
	t := time.Now().UnixMilli()
	art.Utime = t
	res := g.db.WithContext(ctx).Model(&Article{}).Where("id=? AND author_id=?", art.Id, art.AuthorId).
		Updates(map[string]any{
			"title":   art.Title,
			"content": art.Content,
			"status":  art.Status,
			"utime":   art.Utime,
		})

	if res.Error != nil {
		return res.Error
	}

	if res.RowsAffected == 0 {
		//没有行更新，就代表帖子ID和作者ID对不上
		//这里就代表有人在攻击我们,后续可以接入告警，这里我们先打印日志
		g.l.Warn("帖子ID和作者ID不一致")
		return errors.New("更新数据失败")
	}

	return nil
}

// 制作库
type Article struct {
	Id      int64  `gorm:"primaryKey,autoIncrement"`
	Title   string `gorm:"type=varchar(4096)"`
	Content string `gorm:"type=BLOB"`

	//作者ID 需要给一个索引
	AuthorId int64 `gorm:"index"`

	Status uint8

	Ctime int64
	Utime int64
}

// 线上库
type PublishedArticle struct {
	Article
}
